package zserio.extension.common;

import zserio.ast.ArrayInstantiation;
import zserio.ast.ChoiceType;
import zserio.ast.CompatibilityVersion;
import zserio.ast.CompoundType;
import zserio.ast.Field;
import zserio.ast.Package;
import zserio.ast.Root;
import zserio.ast.StructureType;
import zserio.ast.TypeInstantiation;
import zserio.ast.UnionType;
import zserio.tools.ZserioToolPrinter;
import zserio.tools.ZserioVersion;

/**
 * Checks whether specified zserio_compatibility_version is compatible with current zserio generator.
 *
 * This mainly checks that code generated by current zserio generators provides binary compatible BLOBs
 * with BLOBs provided by the specified version.
 */
public final class CompatibilityChecker extends DefaultTreeWalker
{
    @Override
    public boolean traverseTemplateInstantiations()
    {
        return false; // we can check packed arrays directly in templates
    }

    @Override
    public void beginRoot(Root root) throws ZserioExtensionException
    {
        compatibilityVersion = root.getRootPackage().getCompatibilityVersion();
    }

    @Override
    public void beginPackage(Package pkg) throws ZserioExtensionException
    {
        // TODO[Mi-L@]: Pass WarningsConfig to extensions?!
        if (pkg.getCompatibilityVersion() != null)
        {
            if (compatibilityVersion == null)
            {
                ZserioToolPrinter.printWarning(pkg.getCompatibilityVersion(),
                        "Package specifies compatibility version '" +
                                pkg.getCompatibilityVersion().getVersion() +
                                "' while root package specifies nothing!");
            }
            else if (pkg.getCompatibilityVersion().getVersion().compareTo(compatibilityVersion.getVersion()) !=
                    0)
            {
                ZserioToolPrinter.printWarning(pkg.getCompatibilityVersion(),
                        "Package compatibility version '" + pkg.getCompatibilityVersion().getVersion() +
                                "' doesn't match to '" + compatibilityVersion.getVersion() +
                                "' specified in root package!");
            }
        }
    }

    @Override
    public void beginStructure(StructureType structureType) throws ZserioExtensionException
    {
        checkCompoundPackedArrays(structureType);
    }

    @Override
    public void beginChoice(ChoiceType choiceType) throws ZserioExtensionException
    {
        checkCompoundPackedArrays(choiceType);
    }

    @Override
    public void beginUnion(UnionType unionType) throws ZserioExtensionException
    {
        checkCompoundPackedArrays(unionType);
    }

    private void checkCompoundPackedArrays(CompoundType compoundType) throws ZserioExtensionException
    {
        if (compatibilityVersion == null)
            return;

        if (compatibilityVersion.getVersion().compareTo(PACKED_ARRAY_CHANGE_VERSION) < 0)
        {
            for (Field field : compoundType.getFields())
            {
                final TypeInstantiation typeInstantiation = field.getTypeInstantiation();
                if (typeInstantiation instanceof ArrayInstantiation)
                {
                    final ArrayInstantiation arrayInstantiation = (ArrayInstantiation)typeInstantiation;
                    if (arrayInstantiation.isPacked())
                    {
                        ZserioToolPrinter.printError(compatibilityVersion.getLocation(),
                                "Root package requires compatibility with version '" +
                                        compatibilityVersion.getVersion() + "'!");
                        ZserioToolPrinter.printError(field.getLocation(),
                                "Packed arrays binary encoding has been changed in version '" +
                                        PACKED_ARRAY_CHANGE_VERSION + "'!");
                        throw new ZserioExtensionException("Compatibility check failed!");
                    }
                }
            }
        }
    }

    private static final ZserioVersion PACKED_ARRAY_CHANGE_VERSION = new ZserioVersion(2, 5, 0);

    private CompatibilityVersion compatibilityVersion = null;
}
